package com.spring;

import java.beans.Introspector;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

public class JingJiaApplicationContext {
    private Class configClazz;

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();
    private Map<String, Object> singletonBeans = new HashMap<String, Object>();
    private List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();

    public JingJiaApplicationContext(Class configClazz) {
        this.configClazz = configClazz;

        //扫描包路径
        scan(this.configClazz);

        System.out.println(beanDefinitionMap.size());

        /**
         * 初始化非懒加载的单例bean
         * 1. 实例化
         * 2. 依赖注入
         * 3. 初始化前回调
         * 4. 初始化
         * 5。初始化回调
         * 6. 添加到单例池
         */
        initSingletonBeans();
    }

    private void initSingletonBeans() {
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if(ScopeEnum.singleton.equals(beanDefinition.getScope())) {
                loadBean(beanDefinition);
            }
        }
    }

    private Object doCreateBean(BeanDefinition beanDefinition) {
        try {
            Constructor constructor = beanDefinition.getClazzType().getDeclaredConstructor();
            constructor.setAccessible(true);
            Object object = constructor.newInstance();
            return object;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("doCreateBean error");
    }

    private void doDependencyInject(Object object) {
        try {
            for (Field field : object.getClass().getDeclaredFields()) {
                if(field.isAnnotationPresent(Autowired.class)){
                    field.setAccessible(true);
                    field.set(object, loadBean(beanDefinitionMap.get(field.getName())));
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public Object getBean(String name) {
        return loadBean(beanDefinitionMap.get(name));
    }

    private Object loadBean(BeanDefinition beanDefinition) {
        if(Objects.nonNull(beanDefinition)) {
            String beanName = beanDefinition.getBeanName();
            if(ScopeEnum.singleton.equals(beanDefinition.getScope())) {
                if(singletonBeans.containsKey(beanName)) {
                    return singletonBeans.get(beanName);
                }
                //初始化
                Object object = doCreateBean(beanDefinition);
                //依赖注入
                doDependencyInject(object);
                singletonBeans.put(beanName, object);

                //初始化
                if(object instanceof BeanNameAware) {
                    ((BeanNameAware)object).setBeanName(beanName);
                }

                //初始化前
                for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                    beanPostProcessor.postProcessBeforeInitialization(object, beanName);
                }

                //初始化
                if(object instanceof InitializingBean) {
                    ((InitializingBean)object).afterPropertiesSet();
                }

                //初始化前
                for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                    beanPostProcessor.postProcessAfterInitialization(object, beanName);
                }

                return object;
            } else if(ScopeEnum.prototype.equals(beanDefinition.getScope())) {
                return doCreateBean(beanDefinition);
            } else {
                throw new RuntimeException("not support scope:" + beanDefinition.getScope().name());
            }
        } else {
            throw new RuntimeException("invalid arguments");
        }
    }

    private void scan(Class configClazz) {
        if(configClazz.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan =
                    (ComponentScan)configClazz.getAnnotation(ComponentScan.class);
            String packagePath = componentScan.value();
            packagePath = packagePath.replace(".", File.separator);
            System.out.println("path:"+ packagePath);
            //获取包下的所有class
            List<Class> clazzList = scanClazz(packagePath);
            for(Class clazz : clazzList) {
                if(clazz.isAnnotationPresent(Component.class)) {
                    if(clazz.isAssignableFrom(BeanPostProcessor.class)) {
                        doRegisterBeanProcessor(clazz);
                    } else {
                        doRegisterBeanDefinition(clazz);
                    }
                }
            }
        }
    }

    private void doRegisterBeanProcessor(Class clazz) {
        try {
            BeanPostProcessor beanPostProcessor = (BeanPostProcessor) clazz.getDeclaredConstructor()
                    .newInstance();
            beanPostProcessors.add(beanPostProcessor);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    private void doRegisterBeanDefinition(Class clazz){
        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setClazzType(clazz);

        Component component = (Component) clazz.getAnnotation(Component.class);
        String beanName = component.value();
        if("".equals(beanName)) {
            beanName = Introspector.decapitalize(clazz.getSimpleName());
        }
        beanDefinition.setBeanName(beanName);

        if(clazz.isAnnotationPresent(Scope.class)) {
            Scope scope = (Scope) clazz.getAnnotation(Scope.class);
            beanDefinition.setScope(scope.value());
        } else {
            beanDefinition.setScope(ScopeEnum.singleton);
        }
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    private List<Class> scanClazz(String packagePath) {
        List<Class> clazzList = new ArrayList<Class>();
        ClassLoader classLoader = JingJiaApplicationContext.class.getClassLoader();
        URL url = JingJiaApplicationContext.class.getClassLoader().getResource(packagePath);
        try {
            String decodePath = URLDecoder.decode(url.getFile(), "UTF-8");
            File file = new File(decodePath);
            if(file.isDirectory()) {
                for (File item : file.listFiles()) {
                    if(item.getName().endsWith(".class")) {
                        String path = item.getAbsolutePath();
                        String classPath = path.substring(path.indexOf("com"), path.indexOf(".class"));
                        try {
                            classPath = classPath.replace(File.separator, ".");
                            clazzList.add(classLoader.loadClass(classPath));
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return clazzList;
    }
}
